package org.jpro.common.jpa;

import static javax.persistence.criteria.Predicate.BooleanOperator.OR;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.criteria.Predicate;

import org.jpro.common.jpa.specification.BetweenSpecification;
import org.jpro.common.jpa.specification.EqualSpecification;
import org.jpro.common.jpa.specification.GeDSpecification;
import org.jpro.common.jpa.specification.GeSpecification;
import org.jpro.common.jpa.specification.GtDSpecification;
import org.jpro.common.jpa.specification.GtSpecification;
import org.jpro.common.jpa.specification.InSpecification;
import org.jpro.common.jpa.specification.LeDSpecification;
import org.jpro.common.jpa.specification.LeSpecification;
import org.jpro.common.jpa.specification.LikeSpecification;
import org.jpro.common.jpa.specification.LtDSpecification;
import org.jpro.common.jpa.specification.LtSpecification;
import org.jpro.common.jpa.specification.NotEqualSpecification;
import org.jpro.common.jpa.specification.NotInSpecification;
import org.jpro.common.jpa.specification.NotLikeSpecification;
import org.springframework.data.domain.Range;
import org.springframework.data.jpa.domain.Specification;

/**
 * <P>功能描述 : </P>
 * <P>Method Name : PredicateBuilder </P>
 * <P>Author : 王泽浩 </P>
 * <P>E-Mail : 1028625100@qq.com </P>
 * <P>Date : 2017-03-14 15:43:15</P>
 * @since 1.0
 * @param <T> the type parameter
 */
public class PredicateBuilder<T> {

    /**
     * The Operator.
     */
    private final Predicate.BooleanOperator operator;

    /**
     * The Specifications.
     */
    private List<Specification<T>> specifications;

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : Predicate builder </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:15</P>
     * @since 1.0
     * @param operator the operator
     */
    public PredicateBuilder(Predicate.BooleanOperator operator) {
        this.operator = operator;
        this.specifications = new ArrayList<>();
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : eq </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:15</P>
     * @since 1.0
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> eq(String property, Object... values) {
        return eq(true, property, values);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : eq </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:15</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> eq(boolean condition, String property, Object... values) {
        return this.predicate(condition, new EqualSpecification<T>(property, values));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : ne </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:15</P>
     * @since 1.0
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> ne(String property, Object... values) {
        return ne(true, property, values);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : ne </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since x
     * @param condition the condition
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> ne(boolean condition, String property, Object... values) {
        return this.predicate(condition, new NotEqualSpecification<T>(property, values));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : gt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> gt(String property, Number number) {
        return gt(true, property, number);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : gt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> gt(boolean condition, String property, Number number) {
        return this.predicate(condition, new GtSpecification<T>(property, number));
    }

    /**
     * <P>功能描述 : 查询时间大于该时间的 </P>
     * <P>Method Name : gt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-15 16:54:43</P>
     * @since 1.0
     * @param property 属性名称
     * @param date 时间
     * @return the predicate builder
     */
    public PredicateBuilder<T> gt(String property, Date date) {
        return this.gt(true, property, date);
    }

    /**
     * <P>功能描述 : 查询时间大于该时间的 </P>
     * <P>Method Name : gt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-15 16:54:43</P>
     * @since 1.0
     * @param condition 是否增加此查询条件
     * @param property 属性名称
     * @param date 时间
     * @return the predicate builder
     */
    public PredicateBuilder<T> gt(boolean condition, String property, Date date) {
        return this.predicate(condition, new GtDSpecification<T>(property, date));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : ge </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-15 16:58:18</P>
     * @since 1.0
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> ge(String property, Number number) {
        return ge(true, property, number);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : ge </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> ge(boolean condition, String property, Number number) {
        return this.predicate(condition, new GeSpecification<T>(property, number));
    }

    public PredicateBuilder<T> ge(String property, Date date) {
        return ge(true, property, date);
    }

    public PredicateBuilder<T> ge(boolean condition, String property, Date date) {
        return this.predicate(condition, new GeDSpecification<T>(property, date));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : lt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since x
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> lt(String property, Number number) {
        return lt(true, property, number);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : lt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> lt(boolean condition, String property, Number number) {
        return this.predicate(condition, new LtSpecification<T>(property, number));
    }

    /**
     * <P>功能描述 : 查询属性小于该时间的 </P>
     * <P>Method Name : lt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-15 16:41:09</P>
     * @since 1.0
     * @param property 属性名称
     * @param date 比较时间
     * @return the predicate builder
     */
    public PredicateBuilder<T> lt(String property, Date date) {
        return this.predicate(true, new LtDSpecification<T>(property, date));
    }

    /**
     * <P>功能描述 : 查询属性小于该时间的 </P>
     * <P>Method Name : lt </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-15 16:41:09</P>
     * @since 1.0
     * @param condition 是否加入查询条件
     * @param property 属性名称
     * @param date 比较时间
     * @return the predicate builder
     */
    public PredicateBuilder<T> lt(boolean condition, String property, Date date) {
        return this.predicate(condition, new LtDSpecification<T>(property, date));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : le </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> le(String property, Number number) {
        return le(true, property, number);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : le </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param number the number
     * @return the predicate builder
     */
    public PredicateBuilder<T> le(boolean condition, String property, Number number) {
        return this.predicate(condition, new LeSpecification<T>(property, number));
    }

    public PredicateBuilder<T> le(String property, Date date) {
        return le(true, property, date);
    }

    public PredicateBuilder<T> le(boolean condition, String property, Date date) {
        return this.predicate(condition, new LeDSpecification<T>(property, date));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : between </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param property the property
     * @param range the range
     * @return the predicate builder
     */
    public PredicateBuilder<T> between(String property, Range<? extends Comparable<?>> range) {
        return between(true, property, range);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : between </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param range the range
     * @return the predicate builder
     */
    public PredicateBuilder<T> between(boolean condition, String property, Range<? extends Comparable<?>> range) {
        return this.predicate(condition, new BetweenSpecification<T>(property, range));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : like </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param property the property
     * @param patterns the patterns
     * @return the predicate builder
     */
    public PredicateBuilder<T> like(String property, String... patterns) {
        return like(true, property, patterns);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : like </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param patterns the patterns
     * @return the predicate builder
     */
    public PredicateBuilder<T> like(boolean condition, String property, String... patterns) {
        return this.predicate(condition, new LikeSpecification<T>(property, patterns));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : notLike </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param property the property
     * @param patterns the patterns
     * @return the predicate builder
     */
    public PredicateBuilder<T> notLike(String property, String... patterns) {
        return notLike(true, property, patterns);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : notLike </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param patterns the patterns
     * @return the predicate builder
     */
    public PredicateBuilder<T> notLike(boolean condition, String property, String... patterns) {
        return this.predicate(condition, new NotLikeSpecification<T>(property, patterns));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : in </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> in(String property, Object... values) {
        return this.in(true, property, values);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : in </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:16</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> in(boolean condition, String property, Object... values) {
        return this.predicate(condition, new InSpecification<T>(property, values));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : notIn </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:17</P>
     * @since 1.0
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> notIn(String property, Object... values) {
        return this.notIn(true, property, values);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : notIn </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:17</P>
     * @since 1.0
     * @param condition the condition
     * @param property the property
     * @param values the values
     * @return the predicate builder
     */
    public PredicateBuilder<T> notIn(boolean condition, String property, Object... values) {
        return this.predicate(condition, new NotInSpecification<T>(property, values));
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : predicate </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:17</P>
     * @since 1.0
     * @param specification the specification
     * @return the predicate builder
     */
    public PredicateBuilder<T> predicate(Specification specification) {
        return predicate(true, specification);
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : predicate </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:17</P>
     * @since 1.0
     * @param condition the condition
     * @param specification the specification
     * @return the predicate builder
     */
    public PredicateBuilder<T> predicate(boolean condition, Specification specification) {
        if (condition) {
            this.specifications.add(specification);
        }
        return this;
    }

    /**
     * <P>功能描述 : </P>
     * <P>Method Name : build </P>
     * <P>Author : 王泽浩 </P>
     * <P>E-Mail : 1028625100@qq.com </P>
     * <P>Date : 2017-03-14 15:43:17</P>
     * @since 1.0
     * @return the specification
     */
    public Specification<T> build() {
        return (root, query, cb) -> {
            Predicate[] predicates = new Predicate[specifications.size()];
            for (int i = 0; i < specifications.size(); i++) {
                predicates[i] = specifications.get(i).toPredicate(root, query, cb);
            }
            return OR.equals(operator) ? cb.or(predicates) : cb.and(predicates);
        };
    }
}
